﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Web;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using System.Xml;

namespace WFExtended.Twitter
{
    /// <summary>
    /// OAuth WCF Behavior to add OAuth Authorization Header
    /// </summary>
    public class OAuthBehavior : IEndpointBehavior, IClientMessageInspector
    {
        private OAuthTokens _tokens;

        /// <summary>
        /// Initializes a new instance of the <see cref="OAuthBehavior"/> class.
        /// </summary>
        /// <param name="tokens">The tokens.</param>
        public OAuthBehavior(OAuthTokens tokens)
        {
            _tokens = tokens;
        }

        #region IClientMessageInspector

        /// <summary>
        /// Enables inspection or modification of a message after a reply message is received but prior to passing it back to the client application.
        /// </summary>
        /// <param name="reply">The message to be transformed into types and handed back to the client application.</param>
        /// <param name="correlationState">Correlation state data.</param>
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {

        }

        /// <summary>
        /// Enables inspection or modification of a message before a request message is sent to a service.
        /// </summary>
        /// <param name="request">The message to be sent to the service.</param>
        /// <param name="channel">The WCF client object channel.</param>
        /// <returns>
        /// The object that is returned as the <paramref name="correlationState " />argument of the <see cref="M:System.ServiceModel.Dispatcher.IClientMessageInspector.AfterReceiveReply(System.ServiceModel.Channels.Message@,System.Object)" /> method. This is null if no correlation state is used.The best practice is to make this a <see cref="T:System.Guid" /> to ensure that no two <paramref name="correlationState" /> objects are the same.
        /// </returns>
        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {  
            var httpHeader = request.Properties["httpRequest"] as HttpRequestMessageProperty;

            string query = HttpUtility.UrlDecode(request.Headers.To.ToString());
            query = (query.Contains('?')) ? query.Substring(query.IndexOf('?')) : String.Empty;
            System.Collections.Specialized.NameValueCollection parameters =
                     System.Web.HttpUtility.ParseQueryString(query);
            string baseNewUri = request.Headers.To.AbsoluteUri.Split('?')[0];
            string relativeNewUri = String.Empty;
            for (int i = 0; i < parameters.Count; i++)
            {
                relativeNewUri = relativeNewUri + String.Format("{2}{0}={1}", parameters.Keys[i], UrlEncode(parameters[i]), (i == 0) ? "?" : "&");
            }

            request.Headers.To = new Uri(new Uri(baseNewUri), relativeNewUri);

            OAuthAuthorization oAuth = new OAuthAuthorization(_tokens,
                 new Uri(HttpUtility.UrlDecode(request.Headers.To.ToString())), 
                httpHeader.Method);
            httpHeader.Headers.Add("Authorization", oAuth.GenerateAuthorizationHeader());

            if (httpHeader.Headers.AllKeys.Contains("Content-Type")
                && httpHeader.Headers["Content-Type"] == "application/octet-stream")
            { 
                httpHeader.Headers.Remove("Content-Type");
                string contentType = "multipart/form-data; boundary=" + MultipartFormData._BOUNDARY;
                httpHeader.Headers.Add("Content-Type", contentType); 
            }

            return null;
        }

        public static string UrlEncode(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return string.Empty;
            }

            value = Uri.EscapeDataString(value);

            // UrlEncode escapes with lowercase characters (e.g. %2f) but oAuth needs %2F
            value = Regex.Replace(value, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper());

            // these characters are not escaped by UrlEncode() but needed to be escaped
            value = value
                .Replace("(", "%28")
                .Replace(")", "%29")
                .Replace("$", "%24")
                .Replace("!", "%21")
                .Replace("*", "%2A")
                .Replace("'", "%27");

            // these characters are escaped by UrlEncode() but will fail if unescaped!
            value = value.Replace("%7E", "~");

            return value;
        }

        #endregion

        #region IEndpointBehavior

        /// <summary>
        /// Implement to pass data at runtime to bindings to support custom behavior.
        /// </summary>
        /// <param name="endpoint">The endpoint to modify.</param>
        /// <param name="bindingParameters">The objects that binding elements require to support the behavior.</param>
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {

        }

        /// <summary>
        /// Implements a modification or extension of the client across an endpoint.
        /// </summary>
        /// <param name="endpoint">The endpoint that is to be customized.</param>
        /// <param name="clientRuntime">The client runtime to be customized.</param>
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(this);

        }

        /// <summary>
        /// Implements a modification or extension of the service across an endpoint.
        /// </summary>
        /// <param name="endpoint">The endpoint that exposes the contract.</param>
        /// <param name="endpointDispatcher">The endpoint dispatcher to be modified or extended.</param>
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {

        }

        /// <summary>
        /// Implement to confirm that the endpoint meets some intended criteria.
        /// </summary>
        /// <param name="endpoint">The endpoint to validate.</param>
        public void Validate(ServiceEndpoint endpoint)
        {

        }


        #endregion
    }

}
